﻿using System;
using System.ComponentModel;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Diagnostics;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Threading;
using System.Threading;
using System.Threading.Tasks;

using Google.GData.Client;
using Google.GData.Extensions;
using Google.GData.YouTube;
using Google.YouTube;

namespace YoutubeVideoManager
{
    static class VLC
    {
        enum VideoEvent
        {
            Finished
        }
        #region Events
        public static event EventHandler VideoFinished;
        public static event EventHandler CurrentVideoChanged;
        public static event EventHandler VideoEnqueued;
        public static event EventHandler VideoDequeued;
        public static event EventHandler PlaylistCleared;
        #endregion

        #region Properties
        public static Video[] Playlist { get { return playlist.ToArray(); } }
        public static Video Current { get; private set; }
        
        public static bool IsPlaying { get; private set; }
        public static int Length { get; private set; }
        public static int Position { get; private set; }
        #endregion

        #region Fields
        private static readonly BackgroundWorker worker = new BackgroundWorker();
        private static readonly Queue<string> commands = new Queue<string>();
        static TcpClient client = new TcpClient();
        static Process vlc = new Process();
        static int currentIndex = 0;
        static List<Video> playlist = new List<Video>();
        #endregion
        
        #region Methods
        static VLC()
        {
            worker.DoWork += new DoWorkEventHandler(worker_DoWork);
            worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged);
            worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
            worker.WorkerSupportsCancellation = worker.WorkerReportsProgress = true;
        }

        // Viene persa la connessione
        static void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            StopVLC();
        }

        // Viene aggiornata una informazione
        static void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            VideoEvent evt = (VideoEvent)e.ProgressPercentage;
            switch (evt)
            {
                case VideoEvent.Finished:
                    // Riproduco il video successivo (se c'è)
                    Next();
                    if(VideoFinished != null)
                        VideoFinished(null, null);
                    break;
                default:
                    break;
            }
        }

        // Effettua il poolling continuo delle informazioni
        static void worker_DoWork(object sender, DoWorkEventArgs e)
        {
            try
            {
                while (!worker.CancellationPending)
                {
                    // Aggiorno le variabili
                    // Controllo se c'è un video in riproduzione
                    bool oldPlaying = IsPlaying;
                    string str = SendCommand("is_playing");
                    IsPlaying = str[0] == '1';

                    // se prima c'era un video in riproduzione
                    if (oldPlaying)
                    {
                        // se adesso non c'è e Length != 0
                        if (!IsPlaying && Length != 0)
                        {
                            // Allora è finito un video!
                            Position = Length = 0;
                            worker.ReportProgress((int)VideoEvent.Finished);
                        }
                    }

                    // Se c'è un video in riproduzione
                    if (IsPlaying)
                    {
                        // Aggiorno la lunghezza del video
                        str = SendCommand("get_length");
                        str = ClearString(str);

                        int integer;
                        Int32.TryParse(str, out integer);
                        Length = integer;

                        // Aggiorno la posizione del video
                        str = SendCommand("get_time");
                        str = ClearString(str);

                        Int32.TryParse(str, out integer);
                        Position = integer;
                    }

                    // Eseguo un comando dalla coda dei messaggi
                    if (commands.Count > 0)
                    {
                        string command = commands.Dequeue();
                        SendCommand(command);
                    }
                }
            }
            catch { }
        }

        static string SendCommand(string command)
        {
            string str = null;
            try
            {
                // Invio il comando
                StreamWriter sw = new StreamWriter(client.GetStream(), Encoding.ASCII);
                sw.WriteLine(command);
                sw.Flush();

                Thread.CurrentThread.Join(250);

                // Ricevo la risposta
                StreamReader sr = new StreamReader(client.GetStream(), Encoding.ASCII);
                str = sr.ReadLine();
            }
            catch { }
            return str;
        }
        static string ClearString(string str)
        {
            return str.Replace("\0", "").Replace("\r", "").Replace("\n", "");
        }
        static string YoutubeUrl(Video v)
        {
            string quality = "18";
            bool hd = v.YouTubeEntry.ExtensionElements.Count( e => e.XmlName == "hd") > 0;
            if (hd && Properties.Settings.Default.HD)
                quality = "22";
            return string.Format("http://www.youtube.com/v/{0}&fmt={1}", v.VideoId, quality);
        }
        #endregion

        #region Public Methods
        public static void StartVLC()
        {
            Process[] prcs = Process.GetProcessesByName("vlc");
            foreach (var prc in prcs)
            {
                prc.Kill();
            }

            System.Threading.Thread.CurrentThread.Join(100);

            string vlcPath = string.Format("{0}\\VideoLan\\VLC\\vlc.exe", Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles));
            ProcessStartInfo psi = new ProcessStartInfo(vlcPath, "--extraintf rc --rc-host=localhost:9999 --rc-quiet");
            psi.CreateNoWindow = true;
            psi.UseShellExecute = false;
            vlc.StartInfo = psi;
            vlc.Start();

            System.Threading.Thread.CurrentThread.Join(100);

            try
            {
                client.Connect(IPAddress.Parse("127.0.0.1"), 9999);
            }
            catch
            {
                throw;
            }

            worker.RunWorkerAsync();
        }

        public static void StopVLC()
        {
            try
            {
                vlc.Kill();
                vlc.Close();
                worker.CancelAsync();
            }
            catch
            { }
            finally
            {
                vlc.Dispose();
                vlc = null;
            }
        }

        public static void PlayPause()
        {
            commands.Enqueue("pause");
        }

        public static void Play(Video v)
        {
            // Comando VLC
            commands.Enqueue("stop");
            commands.Enqueue("clear");
            commands.Enqueue("add " + YoutubeUrl(v));
            commands.Enqueue("play");

            // Setto il nuovo video
            Current = v;

            // Notifico il cambiamento di video
            if (CurrentVideoChanged != null)
                CurrentVideoChanged(v, null);
        }

        public static void Stop()
        {
            commands.Enqueue("stop");
        }

        public static void Clear()
        {
            Current = null;
            commands.Enqueue("clear");
            playlist.Clear();
            if (PlaylistCleared != null)
            {
                PlaylistCleared(null, null);
            }
        }

        public static void Next()
        {
            if (playlist.Count > 0 && currentIndex < playlist.Count - 1)
            {
                // Ottengo il video successivo
                Video v = playlist[++currentIndex];
                // Riproduco il video
                Play(v);
            }
        }

        public static void Prev()
        {
            // se c'è
            if (playlist.Count > 0 && currentIndex > 0)
            {
                // Ottengo il video precedente
                Video v = playlist[--currentIndex];
                // Riproduco il video
                Play(v);
            }
        }

        public static void AddToPlaylist(Video video)
        {
            if (Current == null)
            {
                Play(video);
            }
            playlist.Add(video);
            if (VideoEnqueued != null)
                VideoEnqueued(video, null);
        }
        public static void RemoveFromPlaylist(int index)
        {
            if (playlist.Count > index)
            {
                Video v = playlist[index];
                playlist.RemoveAt(index);
                if (VideoDequeued != null)
                    VideoDequeued(v, null);
            }
        }
        public static void RemoveFromPlaylist(Video video)
        {
            if (playlist.Contains(video))
            {
                playlist.Remove(video);
                if (VideoDequeued != null)
                    VideoDequeued(video, null);
            }
        }
        public static void Seek(int seconds)
        {
            commands.Enqueue("seek " + seconds);
        }
        #endregion
    }
}